// Copyright (c) 2024, donnie <donnie4w@gmail.com>
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// github.com/donnie4w/gdao

package gdao

import (
	"database/sql"
	. "github.com/donnie4w/gdao/base"
	"github.com/donnie4w/gdao/gdaoSlave"
	"github.com/donnie4w/gdao/gdaoStruct"
	"github.com/donnie4w/gdao/util"
)

func NewDBHandle(db *sql.DB, dbtype DBType) DBhandle {
	return newdbhandle(db, dbtype)
}

var defaultDBhandle DBhandle

func GetDefaultDBHandle() DBhandle {
	return defaultDBhandle
}

// Init initializes the gdao package by binding the provided database connection and data source type.
//
// Parameters:
//
//	db (*sql.DB): An open database connection.
//	dbtype (DBType): The data source type, such as gdao.MYSQL, gdao.POSTGRESQL, etc.
//
// Description:
//
//	This function sets up gdao to work with the specified database connection and data source type.
//	It is typically called at the beginning of the application to configure the gdao package for use with a specific database.
//
// Example:
//
//	db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname")
//	if err != nil {
//	    log.Fatal(err)
//	}
//	defer db.Close()
//
//	gdao.Init(db, gdao.MYSQL)
func Init(db *sql.DB, dbtype DBType) {
	defaultDBhandle = newdbhandle(db, dbtype)
}

var dbContainer = newContainer()

func getDBhandle(classname, tableName string, queryType bool) (r DBhandle) {
	if gdaoSlave.Len() > 0 && queryType {
		if r = gdaoSlave.Get(classname, tableName); r != nil {
			return
		}
	}
	if dbContainer.len() > 0 {
		if h, ok := dbContainer.get(classname); ok {
			return h
		}
		if h, ok := dbContainer.get(tableName); ok {
			return h
		}
	}
	if r = defaultDBhandle; r == nil {
		panic("no available data source could be found")
	}
	return defaultDBhandle
}

func getMapperDBhandle(namespace, id string, queryType bool) (r DBhandle) {
	if namespace != "" && id != "" && queryType && gdaoSlave.Len() > 0 {
		if r = gdaoSlave.GetMapper(namespace, id); r != nil {
			return
		}
	}
	if namespace != "" && id != "" && r == nil && dbContainer.len() > 0 {
		if h, ok := dbContainer.getMapper(namespace, id); ok {
			return h
		}
	}
	return defaultDBhandle
}

func init() {
	GetMapperDBhandle = getMapperDBhandle
}

// BindDataSource binds an open database connection to a specified data source type and list of table names.
//
// Parameters:
//
//	db (*sql.DB): An open database connection.
//	dbtype (DBType): The data source type, such as gdao.MYSQL, gdao.POSTGRESQL, etc.
//	tableNames (...string): A list of table names to be bound to the data source.
//
// Description:
//
//	This method initializes and configures a data source to be used for subsequent data operations. It takes an open database connection,
//	a data source type, and one or more table names that need to be bound. This allows the data source to be used for CRUD operations within the application.
//
// Example:
//
//	db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname")
//	if err != nil {
//	    log.Fatal(err)
//	}
//	defer db.Close()
//
//	BindDataSource(db, gdao.MYSQL, "users", "orders")
func BindDataSource(db *sql.DB, dbtype DBType, tableNames ...string) {
	dbContainer.putTables(newdbhandle(db, dbtype), tableNames...)
}

// BindDataSourceWithClass binds the provided database connection and data source type to a generic table base class.
//
// Parameters:
//
//	db (*sql.DB): An open database connection.
//	dbtype (DBType): The data source type, such as gdao.MYSQL, gdao.POSTGRESQL, etc.
//
// Description:
//
//	This function initializes the generic table base class to work with the specified database connection and data source type.
//
// Type Parameters:
//
//	T: The type of the table base class that is the standardized entity class generated by gdao
//
// Example:
//
//	db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname")
//	if err != nil {
//	    log.Fatal(err)
//	}
//	defer db.Close()
//
//	// assuming dao.Hstest is the standardized entity class generated by gdao
//	BindDataSourceWithClass[dao.Hstest](db, gdao.MYSQL)
func BindDataSourceWithClass[T gdaoStruct.TableClass](db *sql.DB, dbtype DBType) {
	dbContainer.putTables(newdbhandle(db, dbtype), util.Classname[T]())
}

func UnbindDataSource(tableNames ...string) {
	dbContainer.delTables(tableNames...)
}

func UnbindDataSourceWithClass[T gdaoStruct.TableClass]() {
	dbContainer.delTables(util.Classname[T]())
}

// BindMapperDataSource binds the provided database connection and data source type to a specific namespace in the XML mapping files.
//
// Parameters:
//
//	namespace (string): The namespace in the XML mapping files that corresponds to the CRUD to bind.
//	db (*sql.DB): An open database connection.
//	dbtype (DBType): The data source type, such as gdao.MYSQL, gdao.POSTGRESQL, etc.
//
// Description:
//
//	If there is only one data source, the call to the gdao.Init function has already set the data source for all operations.
//	If there are multiple data sources and the xml needs to be set to specify the data source, you can call this function for binding
//
// Example:
//
//	db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname")
//	if err != nil {
//	    log.Fatal(err)
//	}
//	defer db.Close()
//
//	//  assuming users is the namespace of XML mapping
//	BindMapperDataSource("users", db, gdao.MYSQL)
func BindMapperDataSource(namespace string, db *sql.DB, dbtype DBType) {
	dbContainer.putMapper(namespace, newdbhandle(db, dbtype))
}

func UnbindMapperDataSource(namespace string) {
	dbContainer.delMapper(namespace)
}

// BindMapperIdDataSource is similar to the BindMapperDataSource function, but it needs to specify the id of a CURD property
//
// //  assuming users is the namespace of XML mapping and selectHstest is the id of select tag
// BindMapperIdDataSource("users","selectHstest", db, gdao.MYSQL)
func BindMapperIdDataSource(namespace, id string, db *sql.DB, dbtype DBType) {
	dbContainer.putMapperId(namespace, id, newdbhandle(db, dbtype))
}

func UnbindMapperIdDataSource(namespace, id string) {
	dbContainer.delMapperId(namespace, id)
}
